import { EventHandler, Nodes, dom } from '@knno/dom';
import { ANIMATION_DURATION, navigatorPageStyle, navigatorStyle } from './navigator.css';
import { DATA } from '../symbol';

interface NavigatorEventDetail {
	cancel: () => void;
	page: HTMLElement;
}

type NavigatorEvent = CustomEvent<NavigatorEventDetail>;

export interface NavigatorEventMap {
	navback: NavigatorEvent;
}
export type NavigatorEventKey = keyof NavigatorEventMap;
export type NavigatorEventHandler<T extends NavigatorEventKey, O extends Nodes> = (
	this: O,
	event: NavigatorEventMap[T]
) => void;

export interface Navigator {
	on<K extends keyof NavigatorEventMap>(
		event: K,
		listener: NavigatorEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	on<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof NavigatorEventMap>(
		event: K,
		listener: NavigatorEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
}

type CompleteHandler = (status: boolean) => void;

export class Navigator extends Nodes {
	private [DATA] = { rlp: false };

	constructor() {
		super(dom.div(navigatorStyle));
	}

	initialize(nodes: (Nodes | Text)[]): this {
		this.html('').append(dom.div(navigatorPageStyle, 'pushed').append(...nodes));
		return this;
	}

	private createPage(content: Element | Text | (Element | Text)[]): Nodes {
		const page = dom
			.div(navigatorPageStyle)
			.on('navback', (event) => {
				let canceled = false;
				const evt = new CustomEvent<NavigatorEventDetail>('navback', {
					detail: {
						page: page.elems()[0] as HTMLElement,
						cancel: () => {
							canceled = true;
						},
					},
				});
				this.emit(evt);
				event.stopPropagation();
				if (!canceled) {
					this.popPage();
				}
			})
			.append(content);
		return page;
	}

	pushPage(content: Element | Text | (Element | Text)[], complete?: CompleteHandler): this;
	pushPage(content: Element | Text | (Element | Text)[], replace: number, complete?: CompleteHandler): this;
	pushPage(
		content: Element | Text | (Element | Text)[],
		replace?: number | CompleteHandler,
		complete?: CompleteHandler
	): this {
		if (typeof replace === 'function') {
			complete = replace;
			replace = undefined;
		}
		const page = this.createPage(content);
		const lastPage = this.lastChild();
		this.append(page);
		setTimeout(() => {
			lastPage.addClass('inactive');
			page.addClass('pushed');
		}, 32);
		if (typeof complete === 'function' || typeof replace === 'number') {
			setTimeout(() => {
				if (typeof replace === 'number' && !isNaN(replace)) {
					const children = this.children();
					this.removePage(replace, children.length - 1);
				}
				complete?.(true);
			}, ANIMATION_DURATION);
		}
		return this;
	}

	addPage(content: Element | Text | (Element | Text)[], index?: number): this {
		if (typeof index !== 'number' || isNaN(index)) {
			index = this.children().length;
		}
		const page = this.createPage(content).addClass('pushed');
		if (index < this.children().length) {
			const old = this.child(index);
			page.insertTo(this.elems()[0], old).addClass('inactive');
		} else {
			const old = this.child(index - 1);
			old.addClass('inactive');
			this.append(page);
		}
		return this;
	}

	removePage(begin: number, end: number): this {
		const children = this.children();
		if (begin < 0) {
			begin = 0;
		}
		if (end > children.length) {
			end = children.length;
		}
		const middlePages = children.slice(begin, end);
		if (middlePages.length > 0) this.remove(middlePages);
		this.lastChild().removeClass('inactive');
		return this;
	}

	popPage(complete?: CompleteHandler): this;
	popPage(index: number, complete?: CompleteHandler): this;
	popPage(index?: number | CompleteHandler, complete?: CompleteHandler): this {
		const children = this.children()
			.elems()
			.filter((el) => dom.wrap(el).class().includes('pushed'));
		if (typeof index !== 'number' || isNaN(index)) {
			index = children.length - 1;
		}
		if (typeof index === 'function') {
			complete = index;
		}

		const last = this[DATA].rlp ? 1 : 0;
		if (index < last) {
			index = last;
		}
		if (index >= children.length) {
			complete?.(false);
			return this;
		}
		const previous = this.child(index - 1);
		const topest = children[children.length - 1];
		const middlePages = children.slice(index, children.length - 1);
		this.remove(middlePages);
		previous.removeClass('inactive');
		dom.wrap(topest).removeClass('pushed');
		setTimeout(() => {
			this.remove(topest);
			complete?.(true);
		}, ANIMATION_DURATION);
		return this;
	}

	retainLastPage(): boolean;
	retainLastPage(value: boolean): this;
	retainLastPage(value?: boolean): boolean | this {
		if (value === undefined) {
			return this[DATA].rlp;
		}
		this[DATA].rlp = value;
		return this;
	}
}

export function navigator(...className: string[]): Navigator {
	return new Navigator().addClass(...className);
}
